#pragma once

#include <GL/glew.h>
#include <iostream>
#include <vector>

namespace cagd
{
    template <class T>
    class Matrix;
    template <class T>
    class RowMatrix;
    template <class T>
    class ColumnMatrix;

    template <class T>
    std::ostream& operator <<(std::ostream& lhs, const Matrix<T>& rhs);

    template <class T>
    std::istream& operator >>(std::istream& lhs, Matrix<T>& rhs);

    template <class T>
    class Matrix
    {
        friend std::ostream& cagd::operator << <T>(std::ostream& lhs, const Matrix<T>& rhs);
        friend std::istream& cagd::operator >> <T>(std::istream& lhs, Matrix<T>& rhs);

    protected:
        GLuint                         _row_count;
        GLuint                         _column_count;
        std::vector< std::vector<T> >  _data;

    public:
        // special constructor
        Matrix(GLuint row_count = 1, GLuint column_count = 1):
                _row_count(row_count), _column_count(column_count)
        {
            _data.resize(_row_count);
            for (GLuint r = 0; r < _row_count; ++r)
                _data[r].resize(column_count);
        }

        // copy constructor
        Matrix(const Matrix& m)
        {
            _row_count    = m.GetRowCount();
            _column_count = m.GetColumnCount();
            _data.resize(_row_count);
            for (GLuint i = 0; i < _row_count; i++)
            {
                _data[i].resize(_column_count);
                for (GLuint j = 0; j < _column_count; j++)
                    _data[i][j] = m(i, j);
            }
        }

        // assignment operator
        Matrix& operator =(const Matrix& rhs)
        {
            _row_count    = rhs.GetRowCount();
            _column_count = rhs.GetColumnCount();
            _data.resize(_row_count);
            for (GLuint i = 0; i < _row_count; i++)
            {
                _data[i].resize(_column_count);
                for (GLuint j = 0; j < _column_count; j++)
                    _data[i][j] = rhs(i, j);
            }
            return *this;
        }

        // get element by reference
        T& operator() (GLuint row, GLuint column)
        {
            return _data[row][column];
        }
        
        // get copy of an element
        T operator() (GLuint row, GLuint column) const
        {
            return _data[row][column];
        }
        
        GLuint GetRowCount() const { return _row_count; }
        
        GLuint GetColumnCount() const { return _column_count; }
        
        GLboolean SetElement(GLuint row, GLuint column, const T& element)
        {
            _data[row][column] = element;
            return true;
        }
        GLboolean SetRow(GLuint row, const RowMatrix<T>& entire_row)
        {
            if (_column_count != entire_row.GetColumnCount())
                return false;
            for (GLuint j = 0; j < _column_count; j++)
                _data[row][j] = entire_row(j);
            return true;
        }
        GLboolean SetColumn(GLuint column, const ColumnMatrix<T>& entire_column)
        {
            if (_row_count != entire_column.GetRowCount())
                return false;
            for (GLuint i = 0; i < _row_count; i++)
                _data[i][column] = entire_column(i);
            return true;
        }

        virtual GLboolean ResizeRows(GLuint row_count)
        {
            _data.resize(row_count);
            for (GLuint i = _row_count; i < row_count; i++)
                _data[i].resize(_column_count);
            _row_count = row_count;
            return true;
        }
        virtual GLboolean ResizeColumns(GLuint column_count)
        {
            _column_count = column_count;
            for (size_t r = 0; r < _row_count; r++)
                _data[r].resize(_column_count);
            return true;
        }

        virtual ~Matrix() {};
    };


    template <class T>
    class ColumnMatrix: public Matrix<T>
    {
    public:
        ColumnMatrix(GLuint row_count = 1): Matrix<T>(row_count, 1) {}

        T& operator() (GLuint row)
        {
            return Matrix<T>::operator ()(row, 0);
        }

        T operator() (GLuint row) const
        {
            return Matrix<T>::operator ()(row, 0);
        }

        GLboolean ResizeColumns(GLuint column_count)
        {
            return Matrix<T>::ResizeColumns(column_count);
        }
    };


    template <class T>
    class RowMatrix: public Matrix<T>
    {
    public:
        RowMatrix(GLuint column_count = 1): Matrix<T>(1, column_count) {}

        T& operator() (GLuint column)
        {
            return Matrix<T>::operator ()(0, column);
        }

        T operator() (GLuint column) const
        {
            return Matrix<T>::operator ()(0, column);
        }

        GLboolean ResizeRows(GLuint row_count)
        {
            return Matrix<T>::ResizeRows(row_count);
        }
    };
}
